//Copyright 2012 Trevin Liberty
//GNU GPL v3
//Author: Trevin Liberty

#include "GameWorld.h"

#include "Sprite\Tree.h"
#include "Sprite\Flag.h"
#include "Sprite\Fountain.h"
#include "Sprite\Windmill.h"
#include "Sprite\FirePit.h"
#include "Doohickeys\Buildings\Barracks.h"
#include "Doohickeys\Swordsmen.h"
#include "Doohickeys\Archer.h"
#include "Doohickeys\Pathing\AStar.h"

GameWorld::GameWorld(Map* map, int windowWidth, int windowHeight, ALLEGRO_DISPLAY* display) {
	this->map = map;
	this->windowWidth = windowWidth;
	this->windowHeight = windowHeight;
	this->display = display;

	selectedBuilding = NULL;
	barracks = NULL;

	playerRes.deaths = 0;
	playerRes.kills = 0;
	playerRes.gold = 100;

	loadObjects();

	selectedEnts = new list<Entity*>();
	camera = new Camera(windowWidth, windowHeight, map->tileSize, map->numTilesX, map->numTilesY);
	gui = new GUI(windowWidth, windowHeight);
	cursor = new Cursor(display);

	aStar = new AStar(map);
	pathThread = new PathingThread(aStar);
}

//OBJECT TYPES
//0, 1, 2, 3 - FLAGS, colors - black, blue, green, yellow
//4, 5, 6 - TREES, types - A, B, C
//7 - Fountain
//8 - Fire,
void GameWorld::loadObjects() {
	Tree* tree;
	Flag* flag;
	Fountain* f;
	Windmill* w;
	FirePit* fire;
	Barracks* barrack;

	//Search the map for any tile that contains an ObjectType and create an object for it.
	//TODO: Need to remove Object type from mapCell and do all of this differently.
	//	Object Type does nothing after this section. Waste of memory...
	for(int i = 0; i < map->numTilesX; ++i)
		for(int j = 0; j < map->numTilesY; ++j) {
			mapCell* tile = map->mapArray[i][j];

			tree = NULL;
			flag = NULL;
			f = NULL;
			w = NULL;
			fire = NULL;
			barrack = NULL;

			if(tile->objectType < 0)
				continue;

			if(tile->objectType == 0)
				flag = new Flag(1, Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 1)
				flag = new Flag(2, Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 2)
				flag = new Flag(3, Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 3)
				flag = new Flag(4, Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 4)
				tree = new Tree(1, Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 5)
				tree = new Tree(2, Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 6)
				tree = new Tree(3, Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 7)
				f = new Fountain(Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 8)
				w = new Windmill(Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 9)
				fire = new FirePit(Point<float>(i*map->tileSize, j*map->tileSize));
			else if(tile->objectType == 10)
				barrack = new Barracks(map, Point<float>(i*map->tileSize, j*map->tileSize));

			if(flag != NULL) {
				movingSprites.push_back(flag);
				map->mapArray[i][j]->object = flag;
			}
			else if(tree != NULL) {
				movingSprites.push_back(tree);
				map->mapArray[i][j]->object = tree;
			}
			else if(f != NULL) {
				movingSprites.push_back(f);
				map->mapArray[i][j]->object = f;
			}
			else if(w != NULL) {
				movingSprites.push_back(w);
				map->mapArray[i][j]->object = w;
			}
			else if(fire != NULL) {
				movingSprites.push_back(fire);
				map->mapArray[i][j]->object = fire;
			}
			else if(barrack != NULL) {
				barracks = barrack;
				map->mapArray[i][j]->doohickey = barrack;
			}
		}
}

bool GameWorld::hasEntity(int id) {
	list<Entity*>::iterator it;

	for(it = entities.begin(); it != entities.end(); ++it)
		if((*it)->getID() == id)
			return true;
	return false;
}

list<Entity*>::iterator GameWorld::removeEntity(list<Entity*>::iterator it) {
	list<Entity*>::iterator itt = it;
	it++;
	
	Point<int> pos = (*itt)->getMapCellPosition();
	
	//If this happens there's a big problem...
	if(pos.x < 0 || pos.y < 0 || 
		pos.x >= map->numTilesX || 
		pos.y >= map->numTilesY) {
			return it;
	}

	//Remove the Entity from the map
	if(map->mapArray[pos.x][pos.y]->doohickey != NULL && 
		map->mapArray[pos.x][pos.y]->doohickey->getID() == (*itt)->getID())
		map->mapArray[pos.x][pos.y]->doohickey = NULL;
	
	//Remove the Entity if it's in the selectedList
	list<Entity*>::iterator selectItt = selectedEnts->begin();
	for(selectItt; selectItt != selectedEnts->end(); ++selectItt)
		if((*selectItt)->getID() == (*itt)->getID()) {
			selectedEnts->erase(selectItt);
			break;
		}

	delete (*itt);
	entities.erase(itt);
	return it;
}

bool GameWorld::addEntity(Entity* e) {
	list<Entity*>::iterator it;

	//If the Entity already exists, do nothing.
	if(!hasEntity(e->getID())) {
		Point<float> pos = e->getPosition();
		int x = pos.x / map->tileSize;
		int y = pos.y / map->tileSize;

		//If you're trying to add an Entity outside of the mapArray.
		//	VERY BAD!
		if(x < 0 || y < 0 || 
			x >= map->numTilesX || 
		 	y >= map->numTilesY) {
				return false;
		}

		//If you're trying to add the Entity to a spot that already
		//	contains something, you can't
		//	NOT GOOD!
		if(map->mapArray[x][y]->doohickey != NULL)
			return false;

		map->mapArray[x][y]->doohickey = e;
		e->setMapCellPos(Point<int>(x, y));
		entities.push_front(e);
		e->setAStar(aStar);
		return true;
	}
	return false;
}

Entity* GameWorld::getEntity(int id) {
	list<Entity*>::iterator it;

	for(it = entities.begin(); it != entities.end(); ++it)
		if((*it)->getID() == id) {
			return (*it);
		}
	return NULL;
}

void GameWorld::setSelectedEntitiesGoal(Point<float> goal) {
	//If there are no Entity that are selected, do nothing.
	if(!selectedEnts->empty()) {
		Point<int> index;

		index.x = goal.x / map->tileSize;
		index.y = goal.y / map->tileSize;

		if(index.x < 0 || index.x >= map->numTilesX ||
			index.y < 0 || index.y >= map->numTilesY)
			return;
		if(!map->mapArray[index.x][index.y]->walkable)
			return;

		Doohickey* target = map->mapArray[index.x][index.y]->doohickey;

		list<Entity*>::iterator it = selectedEnts->begin();
		for(it; it != selectedEnts->end(); ++it) {
			if(target != NULL && target->getTeam() != (*it)->getTeam())
				(*it)->setTarget(target);
			else
				(*it)->setTarget(NULL);
			(*it)->setGoal(index);
		}
	}
}

bool inRange(int x, int max) {
	if(x >= 0 && x < max)
		return true;
	return false;
}

void GameWorld::getDoohickeysWithin(Point<int> corner1, Point<int> corner2) {
	selectedEnts->clear();

	//Makes corner1 the topLeft corner and corner2 the bottomRight corner.
	if(corner2.x < corner1.x)
		std::swap(corner1.x, corner2.x);
	if(corner2.y < corner1.y)
		std::swap(corner1.y, corner2.y);

	//Keep track of the size of the selectedEnts list.
	//No more than 32 Entities can be selected at any time.
	int i = 0;

	//MOUSE DRAGGED
	list<Entity*>::iterator it;
	for(it = entities.begin(); it != entities.end(); ++it) {
		if(i == 32)
			return;
		//If not on the players team
		if((*it)->getTeam() != 0)
			continue;
		Point<float> pos = (*it)->getPosition();
		Point<int> bounds = (*it)->getImageBound()/2;

		//If Entity is within the corner1/2 bounds.
		if(corner1.x <= pos.x && 
			corner1.y <= pos.y &&
			corner2.x >= pos.x &&
			corner2.y >= pos.y) {
				(*it)->selectTrue();
				selectedEnts->push_back((*it));
				i++;
		}
	}	

	//For checking if the click was within the barracks bounds.
	Point<int> pos = barracks->getPosition();
	Point<int> bounds = barracks->getImageBound();
	if(corner1.x >= pos.x &&
		corner1.y >= pos.y &&
		corner2.x <= pos.x + bounds.x &&
		corner2.y <= pos.y + bounds.y) {
			barracks->selectTrue();
			gui->setBarracksSelected(true);
			selectedBuilding = barracks;
	}
}

void GameWorld::deSelectEntites() {
	list<Entity*>::iterator it;
	for(it = selectedEnts->begin(); it != selectedEnts->end();) {
		(*it)->selectFalse();
		++it;
		selectedEnts->pop_front();
	}
}

void GameWorld::setWindowSize(int width, int height) {
	windowWidth = width; 
	windowHeight = height; 
	camera->setScreenMaxXY(width, height);
	gui->changeScreenDimensions(width, height);
}

void GameWorld::leftClickDown(Point<float> pos){
	Point<float> camPos = camera->getXY();
	cursor->leftDown(pos);
	deSelectEntites();

	Build temp = null;
	//Returns axe, or bow if the barracks is selected and pos is within
	//	a button on the side bar.
	temp = gui->getNewButtonPressBuilding(pos);
	if(temp == axe || temp == bow) {
		if(playerRes.gold >= 25) {
			playerRes.gold -= 25;
			Entity* e;
			if(temp == bow) {
				e = new Swordsmen(100, map, aStar);
				e->initEntity(10, 0, 1, 1.5, 1.5);
			}
			else if(temp == axe) {
				e = new Archer(100, map, aStar);
				e->initEntity(10, 0, 1, 1.5, 10.0);
			}
			if(selectedBuilding != NULL) {
				e->setTeam(0);
				unitBuildQueue.push_back(e);
			}
		}
	}
	else if(selectedBuilding != NULL) {
		selectedBuilding->selectFalse();
		selectedBuilding = NULL;
		gui->setBarracksSelected(false);
	}

	//pos is offset by camPos to get the actual position
	lastLeftClick.x = pos.x + camPos.x;
	lastLeftClick.y = pos.y + camPos.y;
}

void GameWorld::leftClickUp(Point<float> pos) {
	Point<float> camPos = camera->getXY();
	cursor->leftUp();
	Point<int> bottomRight = pos;
	getDoohickeysWithin(lastLeftClick, bottomRight + camPos);
}

void GameWorld::rightClickDown(Point<float> pos){
	Point<float> camPos = camera->getXY();
	cursor->rightDown(pos, camera->getXY());
	setSelectedEntitiesGoal(pos + camPos);
}

void GameWorld::rightClickUp(Point<float> pos){
	cursor->rightUp();
}

void GameWorld::middleClickDown(Point<float> pos) {
	//Set for dragging the camera.
	lastMiddleClick = pos;
}

void GameWorld::middleClickUp(Point<float> pos) {
}


void GameWorld::update() {
	//Update Entities
	list<Entity*>::iterator it;
	for(it = entities.begin(); it != entities.end();) {
		(*it)->update();

		if((*it)->shouldRemove()) {
			if((*it)->getTeam() == 0) 
				playerRes.deaths++;
			else {
				playerRes.kills++;
				playerRes.gold += 25;
			}
			it = removeEntity(it);
			continue;
		}
		++it;
	}

	//Update Sprites
	list<MovingSprite*>::iterator itt;
	for(itt = movingSprites.begin(); itt != movingSprites.end(); ++itt) 
		(*itt)->update();
	
	//Update Buildings
	list<Doohickey*>::iterator itd = buildings.begin();
	for(itd; itd != buildings.end(); ++itd) {
		(*itd)->update();
	}

	//Update unit creation
	if(!unitBuildQueue.empty()) {
		if(barracks->update(unitBuildQueue.front())) {
			addEntity(unitBuildQueue.front());
			unitBuildQueue.erase(unitBuildQueue.begin());
		}
	}

	cursor->update();

	//Update camera position if middle mouse button is down.
	ALLEGRO_MOUSE_STATE state;
	al_get_mouse_state(&state);
	if(al_mouse_button_down(&state, 3)) {
		int dX = state.x;
		int dY = state.y;

		dX = lastMiddleClick.x - dX;
		dY = lastMiddleClick.y - dY;
		camera->addToX(dX);
		camera->addToY(dY);

		lastMiddleClick.x = state.x;
		lastMiddleClick.y = state.y;
	}
}

void GameWorld::draw() {
	Point<float> camPos = camera->getXY();

	int startI = floor(camPos.x / map->tileSize);
	int startJ = floor(camPos.y / map->tileSize);

	//Done because some fringe/object bitmaps are drawn in weird places.
	//	AKA Draws at most 8 tiles off the screen too make sure everything is drawn.
	startI -= 8;
	startJ -= 8;
	while(startI < 0)
		startI++;
	while(startJ < 0)
		startJ++;

	int endI = ceil((camPos.x + windowWidth) / map->tileSize);
	int endJ = ceil((camPos.y + windowHeight) / map->tileSize);

	//Draw Base Tiles
	for(int i = startI; i < endI; i++) {
		for(int j = startJ; j < endJ; j++) {
			mapCell* tile = map->mapArray[i][j];
			if(tile->base >= 0) 
				al_draw_bitmap(base->images[tile->base].image, (map->tileSize * i) - camPos.x, (map->tileSize * j) - camPos.y, 0);
		}
	}

	//Draw Fringe tiles
	for(int j = startJ; j < endJ; j++) {
		for(int i = startI; i < endI; i++) {
			mapCell* tile = map->mapArray[i][j];
			if(tile->fringe >= 0 && tile->fringe != 26 && tile->fringe != 27)
				al_draw_bitmap(fringe->images[tile->fringe].image, (map->tileSize * i) - camPos.x, (map->tileSize * j) - camPos.y, 0);
		}
	}

	//Draw Entities
	list<Entity*>::iterator it;
	for(it = entities.begin(); it != entities.end(); ++it) {
		(*it)->draw(camPos);
	}

	barracks->draw(camPos);

	//Draw Objects
	for(int j = startJ; j < endJ; j++) {
		for(int i = startI; i < endI; i++) {
			mapCell* tile = map->mapArray[i][j];
			if(tile->fringe == 26)
				al_draw_bitmap(fringe->images[26].image, (map->tileSize * i) - camPos.x, (map->tileSize * j) - camPos.y, 0);
			if(tile->fringe == 27)
				al_draw_bitmap(fringe->images[27].image, (map->tileSize * i) - camPos.x, (map->tileSize * j) - camPos.y, 0);
			if(tile->object != NULL) {
				tile->object->draw(camPos);
			}
		}
	}

	//Draw user select rectangle if left mouse button is down
	ALLEGRO_MOUSE_STATE state;
	al_get_mouse_state(&state);
	if(al_mouse_button_down(&state, 1) && selectedBuilding == NULL) {
		al_draw_rectangle(lastLeftClick.x - camPos.x, lastLeftClick.y - camPos.y, state.x, state.y, al_map_rgb(255, 255, 255), 2);
	}

	gui->draw(&entities, camPos, &playerRes);
	cursor->draw(camPos);
}

GameWorld::~GameWorld() {
	Entity* e = entities.front();
	while(!entities.empty()) {
		e = entities.front();
		entities.pop_front();
		e->deleteImages();
		delete e;
	}

	MovingSprite* s;
	while(!movingSprites.empty()) {
		s = movingSprites.front();
		movingSprites.pop_front();
		s->deleteImages();
		delete s;
	}
	
	delete selectedEnts;
	delete pathThread;

	for(int i = 0; i < base->arraySize; i++)
		al_destroy_bitmap(base->images[i].image);

	for(int i = 0; i < fringe->arraySize; i++)
		al_destroy_bitmap(fringe->images[i].image);

	delete base->images;
	delete fringe->images;

	for(int i = 0; i < map->numTilesX; i++) {
		for(int j = 0; j < map->numTilesY; j++) {
			delete map->mapArray[i][j];
		}
		delete map->mapArray[i];
	}
	delete map;
	delete camera;
}